Pro Victoria is an RTS game made in Unreal Engine 4. It is my first RTS project. All RTS mechanics have been designed and implemented by myself. In Pro Victoria you can fight against AI player. AI has been developed using combination of Finite State Machines, Behaviour Trees and Utility AI. The game is featuring typical RTS game elements such as squad formations, micromanagement, resource gathering and recruitment system.
My design assumed that most elements from the RTS definition will be present in the project. Ultimately, elements such as:
- Base-Building
- Resource-Gathering
- Squad Management
- Recruitment System
- Capturable Areas
I decided to remove base-building from the initial design in order to reduce the scope of the project.
Players Manager Class
In order to manage the players effectively I have created a Players Manager class which single instance will be placed in the level. Players Manager can be used as reference by any class which will need an access to any player, control region or other data kept in Players Manager.
Control Regions
Next class to consider is Control Region class. It is a class inherited from Actor. Control Region can be captured by players to receive Victory Points. Player who will fill the Victory Points progress bar first win the game.
HQ Building
HQ building is a class responsible for new squads recruitment and generation of initial income. Each player can have only one HQ building.
Squad Management
Squad control, formations and unit management is managed by Player pawn and Squad class. Here is the general design structure and relationships between these classes. Collectible Items
For collectible Items I also used Factory Design Pattern. All items react to collision with player. Here I used standard SFML method to implement collision (Global Bounds intersection between two sprites). There are 3 items available: Ammo, Health and Coin pickups. After being picked up the item will be destroyed but first the information about what type and what value the item gave to the Player. Ammo will increase the player's ammo amount, health pick up will increase the player's health and coin pickup will be added to the coins found by the player. Coins are the scoring system in our game. Each enemy will drop certain amount of coins to collect. Each item type has its unique sound.
For AI Player I decided to implement Utility AI system as Player will have to determine the best action for particular moment based on the resources, army and other factors. In games like Total War the whole AI system is split into several subsystems where each subsystem is responsible for one area (i.e. diplomacy, resource management, army recruitment etc.). My AI Player is much simpler so in Pro Victoria, it is just a pawn type class with properties, factors and utilities. Based on the Player need, Ai will be able to:
• Attack control region
• Defend owned region
• Attack enemy base
• Defend owned base
• Recruit new squad
For all actions above AI will use array of squads, which includes all squads available for the Player.
Factors Calculation
- (float)PlayerSquads.size()/(float)PopulationMax - Army Size Factor indicating the current player military strength.
- (float)PlayerResources/(float)PlayerResourcesMax - Wealth Factor value that presents current economic strength of the AI player.
- (float)PlayerRegions/(float)ControlRegions.size() - Regions Control Factor value defines the territorial power
- HQ Health Factor is a minor factor just used to let AI know that the main base is in danger.
Utilities
Utilities are calculated using all or only some of the factors with weighting applied in some placed to increase the accuracy of choice.
- Attack Region Utility - The need to attack the nearest uncaptured region (distance from base to region). Running this action also means a small attack on the enemy base to distract the opponent.
- Defend Region Utility - I defines the player need to defend the region. If player captured all available regions but cannot attack enemy base, he will defend the furthest region (distance to base).
- Attack Enemy Base Utility - If this has the highest score, AI player will send all its available forces to attack enemy base.
- Defend Base Utility - It defines how much AI player needs to defend its base.
- Recruit Squad Utility - The need to recruit a new squad.
Ai Player always will take action with the highest score. I have used behaviour tree to help with task execution. The first task in the tree is Analyse Situation. It will be responsible for calling all calculation functions and for comparing the scores. Next nodes in the tree will be fired based on the results.
Squad Member AI
For the Unit AI I used the mixture of Finite States Machines and behaviour tree. AI Unit has defined several states. Based on the current state, the appropriate action will be run using behaviour tree.
Unit States: Idle, Move to Formation, Attack, Flee.
Health And Morale System
Health And Morale System
Each unit has its own health and morale. Morale is calculated based on the current number of squad members. If the morale is very low, the squad member will run away.
Orders
Squads are actors without AI controller. However, AI player and Human Player can give certain orders to the selected squads. These orders include:
- Go to Location
- Change Squad Rotation
- Change Squad Formation
- Change Squad Stance
Squad actor is responsible for positioning Squad Members using position indices in various combinations to create formations.
Formations
The player may order a squad to form a chosen formation. You can choose from 3 formations: square, line and circle.
Stances
The player can set a stance for the selected squad. There are two stances to choose from:
- passive - the squad must be manually controlled by the player. The player must rotate towards the enemy so that all squad members can shoot.
-aggressive - the squad will automatically adjust its rotation if it notices an enemy squad.
Range Of Fire
The yellow cicrle shows the squad range. In order to allow squad members to attack enemy squad, the enemy bannerman has to be within that range.
Bannerman
Bannerman is a special unit within the squad. He cannot attack and squad members cannot attack him. He will be destroyed after squad destruction. Bannerman is used for defining squad range. If enemy bannerman is within the range, bannerman will use event dispatcher to let all squad members know that the enemy is near.
Control Regions can be captured by Players. Each controlled Control Region will generate resources and Victory Points for its owner.
Once the player's squad is within the control area, the process of recapturing will be started. If capturing progress bar will be filled, the Control region will change its player owner to capturing player. It will be also visible in the game as the banner and control area colors will be set to new owner's color.
RTS camera is one of the Human Player class components. Player can move the camera along all three axis. The player pawn (and so the camera) are bounded by Camera Bounding Box Class. Camera Bounding Box can be placed anywhere on the level. However Player Pawn has to be within that box in order to be able to move. Using this simple method I can constrain Player Pawn movement inside each level as I see fit. In one level player may be able to zoom in the camera very close to the ground level. In another one I can contrain it so that player won't be able to zoom in the view at all.
Player can customise the game by changing the global settings and battle settings. Global setting will affect the whole game, however battle setting will be only valid for the next battle.
Global settings include:
- Music On/Off
- Sounds On/Off
- Line Traces On/Off
- ClothSim On/Off
- Particle Effects On/Off
Battle settings include:
- Battle Map Size
- Victory Condition
- Initial Resources
- Control Regions Income
- Player 1 Colour
- Player 2 Colour
All these settings are wrapped in the struct called GameSetupStruct. This struct is stored in Game Instance. Any class and component can then easily access the Game Settings if required by getting the Game Instance.
Below is an example of using current struct to determine which Level should be loaded.